package it.mozzicato.apkwizard.model;

import it.mozzicato.apkwizard.*;
import it.mozzicato.apkwizard.dto.*;
import it.mozzicato.apkwizard.utils.*;

import java.io.*;
import java.util.*;

import javax.xml.parsers.*;

import org.apache.commons.lang.*;
import org.w3c.dom.*;

// Creato il 15/mar/2011

/**
 * @author Roberto Mozzicato
 *
 */
public class PublicResourceTableModel extends AbstractResourceTableModel<PublicResource> {

	public static enum columns implements DescriptionEnum {
		ID("Id"), NAME("Name"), DEFAULT_VALUE("Default Value");
		
		private String desc;

		columns(String desc) {this.desc = desc;}

		@Override
		public String getDescription() {return desc;}
	}
	private HashMap<ResourceTypes, List<PublicResource>> resourceMap = new HashMap<ResourceTypes, List<PublicResource>>();
	private String filterText;
	private List<PublicResource> filteredActiveList;
	private long maxId=0;

	/**
	 * Costruttore
	 * @param projectName
	 */
	public PublicResourceTableModel(String projectName) {
		super(projectName);

		final File publicXml = PathUtils.getFileInProjects(projectName, ApkWizard.SUBPATH_DECOMP, "res", "values", "public.xml");
		File originalPublicXml = PathUtils.getFileInProjects(projectName, ApkWizard.SUBPATH_BACKUP, "res", "values", "public.xml");
		
		final boolean originalExists = originalPublicXml.exists();
		// Create a fast-access map to lookup the resources when will parse the original values from the backup file 
		final HashMap<String, PublicResource> fastMap = new HashMap<String, PublicResource>();
		
		XmlResParser parser = new XmlResParser() {
			@Override
			protected boolean parseElement(Element element, File parsedFile) {
				String id = element.getAttribute("id");
				if(parsedFile == publicXml || !fastMap.containsKey(id)) {
					long numId = Long.parseLong(id.substring(2), 16);
					if(numId > maxId)
						maxId = numId;
					
					ResourceTypes type = EnumUtils.getEnumFromDescription(element.getAttribute("type"), ResourceTypes.values());
	
					if (type != null) {
						List<PublicResource> list = getResourceList(type);
	
						PublicResource res = new PublicResource(type, element.getAttribute("name"), id);
						list.add(res);
						
						// Load the map only if we are parsing the current file and the backup file exists, 
						// else save some resource...
						if(parsedFile == publicXml && originalExists)
							fastMap.put(id, res);
					}
				}
				return true;
			}
		};
		parser.parseFile(publicXml);

		if(originalExists)
			parser.parseFile(originalPublicXml);
		
		fireTableDataChanged();
	}

	public void addResource(PublicResource res) throws IOException {
		if(activeList == null)
			return;
		
		if(res.getId() == null) 
			res.setId("0x" + Long.toHexString(++maxId));

		editResource(res, null, ResourceOperation.ADD);

		activeList.add(res);
		filter(filterText);
	}
	
	public void removeResource(PublicResource res, SortableLocale[] sortableLocales) throws IOException {
		if(activeList == null)
			return;
		
		editResource(res, sortableLocales, ResourceOperation.REMOVE);

		activeList.remove(res);
		filter(filterText);
	}
	
	private void editResource(PublicResource res, SortableLocale[] languageDirs, ResourceOperation operation) throws IOException {
		DocumentBuilder db;
		File xmlfile = PathUtils.getFileInProjects(projectName, ApkWizard.SUBPATH_DECOMP, "res", "values", "public.xml");
		try {
			db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			if(xmlfile.exists()) {
				Document doc = db.parse(xmlfile);
				Node root = doc.getFirstChild();
			
				switch(operation) {
					case ADD: 
						{
							Element child = doc.createElement("public");
							child.setAttribute("type", res.getType().getDescription());
							child.setAttribute("name", res.getName());
							child.setAttribute("id", res.getId());
							root.appendChild(child);
						}
						break;
					case REMOVE:
						{
							Node child = root.getFirstChild();
							while (child != null) {
								if (child instanceof Element) {
									String id = ((Element) child).getAttribute("id");
									if(res.getId().equals(id)) {
										root.removeChild(child);
										break;
									}
								}
								child = child.getNextSibling();
							}
						}
						break;
				}
				
				backupAndSave(doc, xmlfile);

				if(operation == ResourceOperation.REMOVE) {
					// remove the references to the resource on the values* folders
					for (SortableLocale language : languageDirs) {
						xmlfile = PathUtils.getFileInProjects(language.getDirectory().getAbsolutePath(), res.getType().getFileName());
						db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
						if(xmlfile.exists()) {
							doc = db.parse(xmlfile);
							root = doc.getFirstChild();
							Node child = root.getFirstChild();
							while (child != null) {
								if (child instanceof Element) {
									String name = ((Element) child).getAttribute("name");
									if(res.getName().equals(name)) {
										root.removeChild(child);
										break;
									}
								}
								child = child.getNextSibling();
							}
							
							backupAndSave(doc, xmlfile);
						}
					}
				}
			}
			else
				throw new IOException("Unable to find the xml file '" + xmlfile + "'");
		}
		catch (Exception e) {
			e.printStackTrace();
			throw new IOException("Unable to parse the xml file '" + xmlfile + "'");
		}
	}

	public void setActiveType(ResourceTypes type) {
		// Select the selected type list from the global map
		activeType = type;
		activeList = resourceMap.get(type);
		
		if(type.getMaxCount() == 1) {
			// Load both the current values and the original ones (if exist) to detect differences
			final File typeXml = PathUtils.getFileInProjects(projectName, ApkWizard.SUBPATH_DECOMP, "res", "values", type.getFileName());
			File originalTypeXml = PathUtils.getFileInProjects(projectName, ApkWizard.SUBPATH_BACKUP, "res", "values", type.getFileName());
			
			// TODO: inspect every changed version of the file to detect if any language has been changed
			
			final boolean originalExists = originalTypeXml.exists();
			// Creates a fast-access map of the current type's resource list
			final HashMap<String, PublicResource> fastMap = createResMap(activeList);
			XmlResParser parser = new XmlResParser() {
				@Override
				protected boolean parseElement(Element element, File parsedFile) {
					String name = element.getAttribute("name");
					
					if(name != null) {
						PublicResource res = fastMap.get(name);
						if(res != null) {
							if(parsedFile == typeXml) {
								// If we are parsing the current file, set the value as the content of this element.
								// Even if there isn't a backup copy of the file we assume that the original
								// value is the same as the current value.
								res.setValue(element.getTextContent());
								if(!originalExists)
									res.setOriginalValue(res.getValue());
							}
							else {
								// If we are parsing the backup copy the just set the original value
								res.setOriginalValue(element.getTextContent());
							}
						}
					}
					return true;
				}
			};
			parser.parseFile(typeXml);
			
			if(originalExists)
				parser.parseFile(originalTypeXml);
		}
		else if(type == ResourceTypes.Attributes || type == ResourceTypes.Xml || type == ResourceTypes.Layouts) {
			for (PublicResource res: activeList) {
				res.setValue("(Item editing not supported)");
			}
		}
		else {			
			for (PublicResource res: activeList) {
				res.setValue("(Select to show items)");
			}
		}
		
		filter(filterText);
	}

	public Integer getResourceTypeCount(ResourceTypes type) {
		List<PublicResource> list = resourceMap.get(type);
		if(list != null)
			return new Integer(list.size());
		
		return new Integer(0);
	}

	@Override
	public int getColumnCount() {
		return columns.values().length;
	}

	@Override
	public String getColumnName(int columnIndex) {
		return columns.values()[columnIndex].getDescription();
	}

	@Override
	public Class<?> getColumnClass(int columnIndex) {
		return String.class;
	}
	
//	@Override
//	public boolean isCellEditable(int rowIndex, int columnIndex) {
//		return columnIndex==columns.DEFAULT_VALUE.ordinal();
//	}

	@Override
	protected Object getField(PublicResource entry, int columnIndex) {
		switch (columnIndex) {
			case 0:
				return entry.getId();

			case 1:
				return entry.getName();
			
			case 2:
				return entry.getValue();
		}

		return null;
	}

//	@Override
//	protected void setField(PublicResource entry, int columnIndex, Object value) {
//		switch (columnIndex) {
//			case 0 :
//				entry.setId((String) value);
//				break;
//			case 1 :
//				entry.setName((String) value);
//				break;
//			case 2:
//				entry.setValue((String) value);
//		}
//	}
	
	public void filter(String text) {
		filterText=text;
		if(text == null || text.isEmpty()) 
			filteredActiveList = activeList;
		else if (activeList != null) {
			filteredActiveList = new ArrayList<PublicResource>(activeList.size());
			
			for (PublicResource res : activeList) {			
				if(StringUtils.containsIgnoreCase(res.getId(), text) || 
						StringUtils.containsIgnoreCase(res.getName(), text) ||
						StringUtils.containsIgnoreCase(res.getValue(), text))
					filteredActiveList.add(res);
			}
		}
		
		fireTableDataChanged();
	}

	@Override
	public List<PublicResource> getActiveList() {
		return filteredActiveList != null ? filteredActiveList : activeList;
	}

	private List<PublicResource> getResourceList(ResourceTypes type) {
		List<PublicResource> list = resourceMap.get(type);
		if (list == null) {
			list = new ArrayList<PublicResource>();
			resourceMap.put(type, list);
		}
		return list;
	}
}
